/* $Id$ */
/*
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include <pjsua-lib/pjsua.h>
#include <pjsua-lib/pjsua_internal.h>

#if defined(PJSUA_MEDIA_HAS_PJMEDIA) && PJSUA_MEDIA_HAS_PJMEDIA != 0

#define THIS_FILE "pjsua_mcx.c"

/*****************************************************************************
 *
 * Audio media with PJMEDIA backend
 */
PJ_DEF(pj_bool_t) pjsua_call_has_mcx(pjsua_call_id call_id)
{
    pjsua_call *call = &pjsua_var.calls[call_id];
    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
		     PJ_EINVAL);
    return call->mcx_idx >= 0 && call->media[call->mcx_idx].strm.mcx.stream;
}

/* Init pjmedia audio subsystem */
pj_status_t pjsua_mcx_subsys_init()
{
    return PJ_SUCCESS;
}

pj_status_t pjsua_mcx_subsys_start(void)
{
    pj_status_t status = PJ_SUCCESS;

    return status;
}

pj_status_t pjsua_mcx_subsys_destroy()
{
    return PJ_SUCCESS;
}

/*lihongpu add begin*/
static void mcx_floor_callback(pjmedia_mcx_stream *strm, void *user_data,
                           int type, pj_str_t *floor_info)
{
    PJ_LOG(5, (THIS_FILE, "floor_callback 1"));
    PJ_UNUSED_ARG(strm);

    pj_log_push_indent();

    /* For discussions about call mutex protection related to this
     * callback, please see ticket #460:
     *	http://trac.pjsip.org/repos/ticket/460#comment:4
     */
    if (pjsua_var.ua_cfg.cb.on_floor_info)
    {
        pjsua_call_id call_id;
        PJ_LOG(5, (THIS_FILE, "floor_callback 2"));
        call_id = (pjsua_call_id)(pj_ssize_t)user_data;

        pjsua_var.ua_cfg.cb.on_floor_info(call_id, type, floor_info);
    }

    pj_log_pop_indent();
}
/*lihongpu add end*/
/* Internal function: update audio channel after SDP negotiation.
 * Warning: do not use temporary/flip-flop pool, e.g: inv->pool_prov,
 *          for creating stream, etc, as after SDP negotiation and when
 *	    the SDP media is not changed, the stream should remain running
 *          while the temporary/flip-flop pool may be released.
 */
pj_status_t pjsua_mcx_channel_update(pjsua_call_media *call_med,
                                     pj_pool_t *tmp_pool,
                                     pjmedia_mcx_stream_info *si,
                                     const pjmedia_sdp_session *local_sdp,
                                     const pjmedia_sdp_session *remote_sdp)
{
    pjsua_call *call = call_med->call;
    unsigned strm_idx = call_med->idx;
    pj_status_t status = PJ_SUCCESS;

    PJ_UNUSED_ARG(tmp_pool);
    PJ_UNUSED_ARG(local_sdp);
    PJ_UNUSED_ARG(remote_sdp);

    PJ_LOG(4, (THIS_FILE, "pjsua_mcx_channel_update Mcx channel update.. local_sdp->media[strm_idx]->desc.port = %d",local_sdp->media[strm_idx]->desc.port));
    PJ_LOG(4, (THIS_FILE, "pjsua_mcx_channel_update Mcx channel update.. remote_sdp->media[strm_idx]->desc.port = %d",remote_sdp->media[strm_idx]->desc.port));
    pj_log_push_indent();

    // si->rtcp_sdes_bye_disabled = pjsua_var.media_cfg.no_rtcp_sdes_bye;
    
    /* Check if no media is active */
    if (local_sdp->media[strm_idx]->desc.port != 0)
    {

        /* Optionally, application may modify other stream settings here
         * (such as jitter buffer parameters, codec ptime, etc.)
         */
        // si->jb_init = pjsua_var.media_cfg.jb_init;
        // si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
        // si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
        // si->jb_max = pjsua_var.media_cfg.jb_max;
        // si->jb_discard_algo = pjsua_var.media_cfg.jb_discard_algo;

        /* Set SSRC and CNAME */
        si->ssrc = call_med->ssrc;
        si->cname = call->cname;

        /* Set RTP timestamp & sequence, normally these value are intialized
         * automatically when stream session created, but for some cases (e.g:
         * call reinvite, call update) timestamp and sequence need to be kept
         * contigue.
         */
        si->rtp_ts = call_med->rtp_tx_ts;
        si->rtp_seq = call_med->rtp_tx_seq;
        si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;

        /* Create session based on session info. */
        status = pjmedia_mcx_stream_create(pjsua_var.med_endpt, NULL, si,
                                           call_med->tp, NULL,
                                           &call_med->strm.mcx.stream);
        if (status != PJ_SUCCESS)
        {
            goto on_return;
        }

        /* Start stream */
        status = pjmedia_mcx_stream_start(call_med->strm.mcx.stream);
        if (status != PJ_SUCCESS)
        {
            goto on_return;
        }

        // if (call_med->prev_state == PJSUA_CALL_MEDIA_NONE)
        //     pjmedia_stream_send_rtcp_sdes(call_med->strm.a.stream);

        /*lihongpu add begin*/
        PJ_LOG(4, (THIS_FILE, "pjsua_mcx_channel_update set"));
        if (pjsua_var.ua_cfg.cb.on_floor_info)
        {
            pjmedia_mcx_stream_set_floor_callback(call_med->strm.mcx.stream,
                                                  &mcx_floor_callback,
                                                  (void *)(pj_ssize_t)(call->index));
        }
        else
        {
            PJ_LOG(4, (THIS_FILE, "pjsua_mcx_channel_update on_floor_info null"));
        }
        /*lihongpu add end*/

        /* Get the port interface of the first stream in the session.
         * We need the port interface to add to the conference bridge.
         */
        // pjmedia_mcx_stream_get_port(call_med->strm.mcx.stream,
        //                             &call_med->strm.mcx.media_port);

        /* Notify application about stream creation.
         * Note: application may modify media_port to point to different
         * media port
         */
        // if (!call->hanging_up && pjsua_var.ua_cfg.cb.on_stream_created)
        // {
        //     (*pjsua_var.ua_cfg.cb.on_stream_created)(call->index,
        //                                              call_med->strm.a.stream,
        //                                              strm_idx,
        //                                              &call_med->strm.a.media_port);
        // }
    }

on_return:
    pj_log_pop_indent();
    return status;
}

PJ_DEF(void)
pjsua_mcx_conf_connect_param_default(pjsua_conf_connect_param *prm)
{
    pj_bzero(prm, sizeof(*prm));
    prm->level = 1.0;
}

/*
 * Get maxinum number of conference ports.
 */
PJ_DEF(unsigned)
pjsua_mcx_conf_get_max_ports(void)
{
    return pjsua_var.media_cfg.max_media_ports;
}

/*
 * Get current number of active ports in the bridge.
 */
PJ_DEF(unsigned)
pjsua_mcx_conf_get_active_ports(void)
{
    unsigned ports[PJSUA_MAX_CONF_PORTS];
    unsigned count = PJ_ARRAY_SIZE(ports);
    pj_status_t status;

    status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
    if (status != PJ_SUCCESS)
        count = 0;

    return count;
}

/*
 * Enumerate all conference ports.
 */
PJ_DEF(pj_status_t)
pjsua_mcx_enum_conf_ports(pjsua_conf_port_id id[],
                      unsigned *count)
{
    return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned *)id, count);
}

/*
 * Get information about the specified conference port
 */
PJ_DEF(pj_status_t)
pjsua_mcx_conf_get_port_info(pjsua_conf_port_id id,
                         pjsua_conf_port_info *info)
{
    pjmedia_conf_port_info cinfo;
    unsigned i;
    pj_status_t status;
    PJ_LOG(4, (THIS_FILE, "pjsua_mcx_conf_get_port_info"));

    status = pjmedia_conf_get_port_info(pjsua_var.mconf, id, &cinfo);
    if (status != PJ_SUCCESS)
        return status;

    pj_bzero(info, sizeof(*info));
    info->slot_id = id;
    info->name = cinfo.name;
    pjmedia_format_copy(&info->format, &cinfo.format);
    info->clock_rate = cinfo.clock_rate;
    info->channel_count = cinfo.channel_count;
    info->samples_per_frame = cinfo.samples_per_frame;
    info->bits_per_sample = cinfo.bits_per_sample;
    info->tx_level_adj = ((float)cinfo.tx_adj_level) / 128 + 1;
    info->rx_level_adj = ((float)cinfo.rx_adj_level) / 128 + 1;

    /* Build array of listeners */
    info->listener_cnt = cinfo.listener_cnt;
    for (i = 0; i < cinfo.listener_cnt; ++i)
    {
        info->listeners[i] = cinfo.listener_slots[i];
    }

    return PJ_SUCCESS;
}

/*
 * Add arbitrary media port to PJSUA's conference bridge.
 */
PJ_DEF(pj_status_t)
pjsua_mcx_conf_add_port(pj_pool_t *pool,
                    pjmedia_port *port,
                    pjsua_conf_port_id *p_id)
{
    pj_status_t status;
    PJ_LOG(4, (THIS_FILE, "pjsua_mcx_conf_add_port"));
    status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
                                   port, NULL, (unsigned *)p_id);
    if (status != PJ_SUCCESS)
    {
        if (p_id)
            *p_id = PJSUA_INVALID_ID;
    }

    return status;
}

/*
 * Remove arbitrary slot from the conference bridge.
 */
PJ_DEF(pj_status_t)
pjsua_mcx_conf_remove_port(pjsua_conf_port_id id)
{
    pj_status_t status;
    PJ_LOG(4, (THIS_FILE, "pjsua_mcx_conf_remove_port"));
    status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
    pjsua_check_snd_dev_idle();

    return status;
}

/*
 * Establish unidirectional media flow from souce to sink.
 */
PJ_DEF(pj_status_t)
pjsua_mcx_conf_connect(pjsua_conf_port_id source,
                   pjsua_conf_port_id sink)
{
    pjsua_conf_connect_param prm;
    PJ_LOG(4, (THIS_FILE, "pjsua_mcx_conf_connect"));
    pjsua_conf_connect_param_default(&prm);
    return pjsua_conf_connect2(source, sink, &prm);
}

/*
 * Establish unidirectional media flow from souce to sink, with signal
 * level adjustment.
 */
PJ_DEF(pj_status_t)
pjsua_mcx_conf_connect2(pjsua_conf_port_id source,
                    pjsua_conf_port_id sink,
                    const pjsua_conf_connect_param *prm)
{
    pj_status_t status = PJ_SUCCESS;

    PJ_LOG(4, (THIS_FILE, "pjsua_mcx_conf_connect2 %s connect: %d --> %d",
               (pjsua_var.is_mswitch ? "Switch" : "Conf"),
               source, sink));
   
    return status;
}

/*
 * Disconnect media flow from the source to destination port.
 */
PJ_DEF(pj_status_t)
pjsua_mcx_conf_disconnect(pjsua_conf_port_id source,
                      pjsua_conf_port_id sink)
{
    pj_status_t status;
    return PJ_SUCCESS;
}

/*
 * Adjust the signal level to be transmitted from the bridge to the
 * specified port by making it louder or quieter.
 */
PJ_DEF(pj_status_t)
pjsua_mcx_conf_adjust_tx_level(pjsua_conf_port_id slot,
                           float level)
{
    return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
                                        (int)((level - 1) * 128));
}

/*
 * Adjust the signal level to be received from the specified port (to
 * the bridge) by making it louder or quieter.
 */
PJ_DEF(pj_status_t)
pjsua_mcx_conf_adjust_rx_level(pjsua_conf_port_id slot,
                           float level)
{
    return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
                                        (int)((level - 1) * 128));
}

/*
 * Get last signal level transmitted to or received from the specified port.
 */
PJ_DEF(pj_status_t)
pjsua_mcx_conf_get_signal_level(pjsua_conf_port_id slot,
                            unsigned *tx_level,
                            unsigned *rx_level)
{
    return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
                                         tx_level, rx_level);
}

/*****************************************************************************
 * File player.
 */

static char *get_basename(const char *path, unsigned len)
{
    char *p = ((char *)path) + len;

    if (len == 0)
        return p;

    for (--p; p != path && *p != '/' && *p != '\\';)
        --p;

    return (p == path) ? p : p + 1;
}

/* Get format name */
static const char *get_fmt_name(pj_uint32_t id)
{
    static char name[8];

    if (id == PJMEDIA_FORMAT_L16)
        return "PCM";
    pj_memcpy(name, &id, 4);
    name[4] = '\0';
    return name;
}
#endif /* PJSUA_MEDIA_HAS_PJMEDIA */
